using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace BOL.Maths
{
    /// <summary>
    /// Represents a Vector generic class.
    /// </summary>
    public class Vector<T> : ICloneable, IEnumerable<T>, IGrouping<TKey, TElement>, IEquatable<Vector<T>>
    {
        public TKey Key { get; set; }
    }
    {
        #region Private variables

        private readonly int _length;
        private readonly T[] _elements;
        private bool _isColumnVector;

        #endregion

        #region Public properties

        public int Length { get { return _length; } }

        public T[] Elements { get { return _elements; } }

        public T this[int index]
        {
            get
            {
                if (index < 0 || index >= _length)
                    throw new ArgumentOutOfRangeException();

                return _elements[index];
            }
            set
            {
                if (index < 0 || index >= _length)
                    throw new ArgumentOutOfRangeException();

                _elements[index] = value;
            }
        }

        public bool IsColumnVector { get { return _isColumnVector; } }

        #endregion

        #region Constructors

        public Vector(IEnumerable<T> elements, bool isColumnVector = false)
        {
            if (elements == null)
                throw new ArgumentNullException("elements");

            _elements = elements.ToArray();
            _length = _elements.Length;
            
            _isColumnVector = isColumnVector;
        }

        public Vector(int length, bool isColumnVector = false)
            : this(new T[length], isColumnVector) { }

        #endregion

        #region Operators implementation

        public static explicit operator Matrix<T>(Vector<T> v)
        {
            return new Matrix<T>(v);
        }

        public static bool operator ==(Vector<T> u, Vector<T> v)
        {
            if (u == null)
                throw new ArgumentNullException("u");

            if (v == null)
                throw new ArgumentNullException("v");

            return u.Equals(v);
        }

        public static bool operator !=(Vector<T> u, Vector<T> v)
        {
            if (u == null)
                throw new ArgumentNullException("u");
            if (v == null)
                throw new ArgumentNullException("v");

            return !u.Equals(v);
        }

        #endregion

        #region ICloneable implementation

        public Vector<T> Clone()
        {
            return new Vector<T>(_elements.Select(x => x).ToArray(), _isColumnVector);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

        #region IEnumerable, IEnumerable<T> implementation

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _elements.GetEnumerator();
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (var i = 0; i < _length; i++)
                yield return _elements[i];
        }

        #endregion

        #region Public methods

        public void Transpose()
        {
            _isColumnVector = !_isColumnVector;
        }

        #endregion

        #region IEquatable<Vector<T>> interface

        public bool Equals(Vector<T> other)
        {
            if (_length != other._length)
                return false;

            if (_isColumnVector != other._isColumnVector)
                return false;

            for (var i = 0; i < _length; i++)
                if (!_elements[i].Equals(other._elements[i]))
                    return false;

            return true;
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Elements.Aggregate(1, (hashCode, current) => hashCode ^ current.GetHashCode()) ^ IsColumnVector.GetHashCode();
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is Vector<T>))
                throw new InvalidCastException("The 'other' argument is not a Vector<T> object.");

            return Equals(other as Vector<T>);
        }

        public override string ToString()
        {
            return String.Format("Vector<{0}>[{1}] ", typeof(T), _length);
        }

        #endregion
    }
}
